package com.anlogic.sdk.utils;

import java.io.File;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;

public class ECCUtils {
	public static final String ECDSA256 = "ecdsa256";
	public static final String SM2 = "sm2";
	
	public static final String SHA256 = "sha256";
	public static final String SM3 = "sm3";
	
	public static final Provider PROVIDER = new BouncyCastleProvider();

	static {
		Security.addProvider(PROVIDER);
	}

	public static String getEnctriptKeyHashValue(String path) {
		String keyHashValue = "";

		String[] keyLines = FileUtils.readLines(new File(path));

		Pattern pattern = Pattern.compile("(?:Key\\s+\\d+\\s+)([0-9a-fA-F]+)");

		for (String line : keyLines) {
			Matcher m = pattern.matcher(line);
			if (m.find()) {
				keyHashValue = m.group(1);
			}
		}

		return keyHashValue;
	}
	
	public static String getEnctriptKeyHashValueAlign64(String path) {
		String keyHashValue = "";

		String[] keyLines = FileUtils.readLines(new File(path));

		Pattern pattern = Pattern.compile("(?:Key\\s+\\d+\\s+)([0-9a-fA-F]+)");

		for (String line : keyLines) {
			Matcher m = pattern.matcher(line);
			if (m.find()) {
				keyHashValue = m.group(1);
			}
		}
			
		while (keyHashValue.length() < 64) {
			keyHashValue = keyHashValue + "0";
		}

		return keyHashValue;
	}
	
	public static String getPublicKeyAlgorithm(String path) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
		String keyString = FileUtils.read(new File(path));
		keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "");
		byte[] keyBytes = Base64.getMimeDecoder().decode(keyString);

		KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
		KeySpec keySpec = new X509EncodedKeySpec(keyBytes);
		BCECPublicKey publicKey = (BCECPublicKey) keyFactory.generatePublic(keySpec);
		
		ECNamedCurveSpec parameterSpec = (ECNamedCurveSpec)publicKey.getParams();
		
		String algorithm = "";
		if (parameterSpec.getName().contains("secp256")) {
			algorithm = ECDSA256;
		} else if (parameterSpec.getName().contains("sm2")) {
			algorithm = SM2;
		}
		
		return algorithm;
	}

	public static String getPublicKeyHashValue(String path)
			throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
		String keyString = FileUtils.read(new File(path));
		keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "");
		byte[] keyBytes = Base64.getMimeDecoder().decode(keyString);

		KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
		KeySpec keySpec = new X509EncodedKeySpec(keyBytes);
		BCECPublicKey publicKey = (BCECPublicKey) keyFactory.generatePublic(keySpec);

		byte[] xBytes = publicKey.getW().getAffineX().toByteArray();
		byte[] yBytes = publicKey.getW().getAffineY().toByteArray();
		if (xBytes.length == 33) {
			xBytes = Arrays.copyOfRange(xBytes, 1, xBytes.length);
		}
		if (yBytes.length == 33) {
			yBytes = Arrays.copyOfRange(yBytes, 1, yBytes.length);
		}
		
		byte[] totalBytes = concat(xBytes, yBytes);
		totalBytes = reverseBytesOrder(totalBytes);
		
		ECNamedCurveSpec parameterSpec = (ECNamedCurveSpec)publicKey.getParams();
		
		String algorithm = SHA256;
		if (parameterSpec.getName().contains("ecdsa256")) {
			algorithm = SHA256;
		} else if (parameterSpec.getName().contains("sm2")) {
			algorithm = SM3;
		}
		
		MessageDigest digest = MessageDigest.getInstance(algorithm, PROVIDER);

		byte[] hashBytes = digest.digest(totalBytes);

		return bytesToHexString(hashBytes);
	}

	public static String getMessageDigest(String msg, String algorithm) throws NoSuchAlgorithmException {

		byte[] totalBytes = hexStringToBytes(msg);
		totalBytes = reverseBytesOrder(totalBytes);

		MessageDigest digest = MessageDigest.getInstance(algorithm, PROVIDER);

		byte[] hashBytes = digest.digest(totalBytes);

		return bytesToHexString(hashBytes);
	}

	public static String reverseEndOrder(String data) {
		String reverseEndData = "";
		for (int i = data.length() - 8; i >= 0; i -= 8) {
			String tmp = data.substring(i, i + 8);
			reverseEndData += tmp;
		}
		return reverseEndData;
	}

	public static String reverseBytesOrder(String data) {
		String reverseData = "";
		for (int i = 0; i < data.length(); i += 8) {
			String tmp = data.substring(i, i + 8);
			for (int j = 0; j < 8; j += 2) {
				reverseData += tmp.substring(6 - j, 8 - j);
			}
		}
		return reverseData;
	}

	public static byte[] reverseBytesOrder(byte[] data) {
		byte[] reverseData = Arrays.copyOf(data, data.length);
		for (int i = 0; i < data.length; i += 4) {
			byte[] tmp = Arrays.copyOfRange(data, i, i + 4);
			for (int j = 0; j < 4; j++) {
				reverseData[i + j] = tmp[3 - j];
			}
		}
		return reverseData;
	}

	public static String bytesToHexString(byte[] data) {
		StringBuilder hexString = new StringBuilder();
		for (byte b : data) {
			String hex = Integer.toHexString(0xFF & b);
			if (hex.length() == 1) {
				hexString.append('0');
			}
			hexString.append(hex);
		}
		return hexString.toString().toUpperCase();
	}

	public static byte[] hexStringToBytes(String data) {
		int arrayLength = data.length() >> 1;
		byte[] byteArray = new byte[arrayLength];
		for (int i = 0; i < arrayLength; i++) {
			byte highDit = (byte) (Character.digit(data.charAt(i * 2), 16) & 0xFF);
			byte lowDit = (byte) (Character.digit(data.charAt(i * 2 + 1), 16) & 0xFF);
			byteArray[i] = (byte) (highDit << 4 | lowDit);
		}
		return byteArray;
	}

	public static byte[] concat(byte[] first, byte[] second) {
		byte[] result = Arrays.copyOf(first, first.length + second.length);
		System.arraycopy(second, 0, result, first.length, second.length);
		return result;
	}

	public static String getBinaryStr(String hexStr) {
		String binStr = new BigInteger(hexStr, 16).toString(2);

		while (binStr.length() < 64) {
			binStr = "0" + binStr;
		}

		binStr = new StringBuffer(binStr).reverse().toString();

		return binStr;
	}

	public static int xorBinaryStr(String binStr) {
		int ret = Character.digit(binStr.charAt(0), 2);
		for (int i = 1; i < binStr.length(); i++) {
			ret ^= Character.digit(binStr.charAt(i), 2);
		}
		return ret;
	}

	public static String eccEncode(String hexStr) {
		StringBuffer eccParityBuffer = new StringBuffer();

		String binStr = getBinaryStr(hexStr);

		int eccParity7 = xorBinaryStr(binStr.substring(57, 64));
		int eccParity6 = xorBinaryStr(binStr.substring(26, 57));
		int eccParity5 = xorBinaryStr(binStr.substring(11, 26) + binStr.substring(41, 57));
		int eccParity4 = xorBinaryStr(binStr.substring(4, 11) + binStr.substring(18, 26) + binStr.substring(33, 41)
				+ binStr.substring(49, 57));
		int eccParity3 = xorBinaryStr(binStr.substring(1, 4) + binStr.substring(7, 11) + binStr.substring(14, 18)
				+ binStr.substring(22, 26) + binStr.substring(29, 33) + binStr.substring(37, 41)
				+ binStr.substring(45, 49) + binStr.substring(53, 57) + binStr.substring(60, 64));
		int eccParity2 = xorBinaryStr(binStr.substring(0, 1) + binStr.substring(2, 4) + binStr.substring(5, 7)
				+ binStr.substring(9, 11) + binStr.substring(12, 14) + binStr.substring(16, 18)
				+ binStr.substring(20, 22) + binStr.substring(24, 26) + binStr.substring(27, 29)
				+ binStr.substring(31, 33) + binStr.substring(35, 37) + binStr.substring(39, 41)
				+ binStr.substring(43, 45) + binStr.substring(47, 49) + binStr.substring(51, 53)
				+ binStr.substring(55, 57) + binStr.substring(58, 60) + binStr.substring(62, 64));
		int eccParity1 = xorBinaryStr(
				binStr.substring(0, 2) + binStr.substring(3, 5) + binStr.substring(6, 7) + binStr.substring(8, 9)
						+ binStr.substring(10, 12) + binStr.substring(13, 14) + binStr.substring(15, 16)
						+ binStr.substring(17, 18) + binStr.substring(19, 20) + binStr.substring(21, 22)
						+ binStr.substring(23, 24) + binStr.substring(25, 27) + binStr.substring(28, 29)
						+ binStr.substring(30, 31) + binStr.substring(32, 33) + binStr.substring(34, 35)
						+ binStr.substring(36, 37) + binStr.substring(38, 39) + binStr.substring(40, 41)
						+ binStr.substring(42, 43) + binStr.substring(44, 45) + binStr.substring(46, 47)
						+ binStr.substring(48, 49) + binStr.substring(50, 51) + binStr.substring(52, 53)
						+ binStr.substring(54, 55) + binStr.substring(56, 57) + binStr.substring(57, 58)
						+ binStr.substring(59, 60) + binStr.substring(61, 62) + binStr.substring(63, 64));

		eccParityBuffer.append(eccParity1);
		eccParityBuffer.append(eccParity2);
		eccParityBuffer.append(eccParity3);
		eccParityBuffer.append(eccParity4);
		eccParityBuffer.append(eccParity5);
		eccParityBuffer.append(eccParity6);
		eccParityBuffer.append(eccParity7);

		int eccParity0 = xorBinaryStr(binStr + eccParityBuffer);

		eccParityBuffer.insert(0, eccParity0);

		return eccParityBuffer.toString();
	}

	public static int[] getEcc(String hexStr) {
		int[] parities = new int[4];
		for (int i = 0; i < 4; i++) {
			String eccParity = eccEncode(hexStr.substring(i * 16, i * 16 + 16));
			int parity = 0;
			for (int j = 0; j < eccParity.length(); j++) {
				parity += Math.pow(2, j) * Character.digit(eccParity.charAt(j), 2);
			}
			parities[3 - i] = parity;
		}
		return parities;
	}
}
